Route Module 的設計可以很多樣, 最重要的功能即定義路由 ,所以要在 constructor()
去觸發路由註冊,這就是 Route Module 的核心,我們新增一個 route.base.ts
並放在 bases
資料夾下,以下是 route.base.ts
的內容,可以看到有一個抽象方法 registerRoute()
,它就是註冊路由用的:
import { Router } from 'express';
export abstract class RouteBase {
public router = Router();
constructor() {
this.initial();
}
protected initial(): void {
this.registerRoute();
}
protected abstract registerRoute(): void;
}
接著我們要建置一個 app.routing.ts
作為整個 Express App 的路由集束點,為什麼不是在 App
做集束呢?因為 App
本身的工作不是在定義路由上,是在做 Express App 的相關設置,這樣能夠切割的較乾淨。下方為 app.routing.ts
的內容:
import { RouteBase } from './bases/route.base';
export class AppRoute extends RouteBase {
constructor() {
super();
}
protected registerRoute(): void {
// 設置路由
}
};
最後就是在 App
中進行串接,所以修改一下 app.ts
,將 AppRoute
建立實例,並修改 registerRoute()
:
private route = new AppRoute();
private registerRoute(): void {
this.app.use('/', this.route.router);
}
在設計完 RouteBase
後,就可以來規劃我們的 API 路由了,基本上我習慣以 /api
作為 API 的根節點,但我們不去改變 app.ts
中的配置,我們另外新增一個 api.routing.ts
的路由模組,在 app.routing.ts
中使用它。下圖為模組配置關係圖:
? Route 為後面後提到的配置項目,暫時不用管它,只需要知道有這東西就好
下方為更改後的資料夾結構:
├── src
| ├── index.ts
| ├── app.ts
| ├── app.routing.ts // 本篇新增
| ├── bases
| | └── route.base.ts // 本篇新增
| ├── main // 本篇新增
| | └── api // 本篇新增
| | ├── api.routing.ts // 本篇新增
| | └── todo // 本篇新增
| | └── todo.routing.ts // 本篇新增
| ├── environments
| | ├── development.env
| | └── production.env
| └── validators
| ├── index.ts
| └── email.validator.ts
├── package.json
└── tsconfig.json
從最內層的 TodoRoute
開始做起,我們在這層做一個測試用的 API,下方為 todo.routing.ts
的程式碼:
import { RouteBase } from '../../../bases/route.base';
export class TodoRoute extends RouteBase {
constructor() {
super();
}
protected registerRoute(): void {
this.router.get('/test', (req, res, next) => res.send('todo test.'));
}
}
透過 ApiRoute
來連結 TodoRoute
的路由,下方為 api.routing.ts
的程式碼:
import { RouteBase } from '../../bases/route.base';
import { TodoRoute } from './todo/todo.routing';
export class ApiRoute extends RouteBase {
private todoRoute = new TodoRoute();
constructor() {
super();
}
protected registerRoute(): void {
this.router.use('/todo', this.todoRoute.router);
}
}
這裡要修改 AppRoute
的配置:
import { RouteBase } from './bases/route.base';
import { ApiRoute } from './main/api/api.routing';
export class AppRoute extends RouteBase {
private apiRoute = new ApiRoute();
constructor() {
super();
}
protected registerRoute(): void {
this.router.use('/api', this.apiRoute.router);
}
};
在瀏覽器中輸入 http://localhost:3000/api/todo/test 會看到以下結果:
為了方便維護系統,我在這邊與大家分享我自己的規範:
在 Route Module 中,只做與路由相關的事情是理所當然的,所以應盡量避免不必要的業務邏輯等程式片段,下方的程式碼就不符合此定義,應該要 把中介軟體切割出去 ,這部分後面會再告訴大家要怎麼切割,所以這篇還是用這樣的方式寫測試:
protected registerRoute(): void {
this.router.get('/test', (req, res, next) => res.send('todo test.'));
}
在包裝成 Route Module 的時候,應該要盡量把過於重複的路徑資源切割成另一個 Route Module,以下方例子來說,同樣都是在 /users
與 /orders
下的資源,我會額外切出 UserRoute
與 OrderRoute
,否則會看到下面的大雜燴:
protected registerRoute(): void {
this.router.get('/', (req, res, next) => res.end());
this.router.get('/users', (req, res, next) => res.end());
this.router.get('/users/:id', (req, res, next) => res.end());
this.router.post('/users', (req, res, next) => res.end());
this.router.patch('/users/:id', (req, res, next) => res.end());
this.router.delete('/users/:id', (req, res, next) => res.end());
this.router.get('/orders', (req, res, next) => res.end());
this.router.get('/orders/:id', (req, res, next) => res.end());
}
Route Module 的核心功能即為定義路由相關策略,這個觀念是很重要的,以往在寫 Express 的時候,就是把路由跟其他雜七雜八的混在一起寫,最後的下場就是很難維護,所以前面有提到要將業務邏輯切分出去,那究竟要如何切分呢?切分出去的我們又如何稱呼呢?答案就是設計 Controller,下一篇將告訴大家我的 Controller 設計方法!
你好,
我試著實作您的Route Module,但是執行時遇到"undefined"錯誤,我試著插log印出"this.apiRoute"此變數,確實為"undefined",請問您有遇到相同的問題嗎?
請問有其他資訊嗎?
你好,我這邊也發生了相同的狀況。
根據我研究之後發現,應該是因為 base class 與 derived class 的初始化順序的問題。根據 這篇文章,base class 的 constructor 執行完之後才會初始化 derived class 的 properties,因此當 RouteBase
執行到 registerRoute
的時候,apiRoute
變數尚未被初始化,因此就發生了上面的錯誤。我不太確定以前為什麼不會發生,也許是某次 TypeScript 更新後產生的問題。
我的解決辦法就是不在 property 初始化 apiRoute
,而是直接在 registerRoute
裡面 new ApiRoute
,其他類似的地方也做類似的處理,就可以正常執行了。
我有遇到相同問題,也試著驗證過這件事情,看來確實是因為順序的問題,驗證的程式碼如下:
abstract class Base {
constructor() {
this.setData();
}
protected abstract setData(): void;
}
class Child extends Base {
private prop = (() => {
console.log('==prop==')
return {key: 'value'};
})()
constructor() {
super()
}
protected setData(): void {
console.log('==setData==')
}
}
const child = new Child();
會先後出現
==setData==
==prop==